/**
 * Copyright (c) 2017-2023 IceGPS. All rights reserved.
 *
 * @file qxwz_user_impl.c
 * @brief QXWZ SDK 用户需要实现网络通信内存分配互斥锁等函数 ，还需要对Lua需要的函数做封装
 * 需要暴露给LuatOS的函数
 * 1、qxwz_sdk_tick，需要100ms调用一次
 * 2、qxwz_sdk_config，配置SDK参数
 * 3、qxwz_sdk_init，初始化
 * 4、qxwz_sdk_auth，鉴权
 * 5、qxwz_sdk_start_with_params，启动能力
 * 6、qxwz_sdk_upload_gga_by_cap_id，用于上传GGA

 * 除了上面的函数之外，还需要实现5个回调函数
 * 11、demo_on_status，运行状态的回调函数
 * 12、demo_on_data，获取差分数据的回调函数
 * 13、demo_on_auth，鉴权是否成功的回调函数
 * 14、demo_on_start，能力启动是否成功的回调函数
 * 15、demo_on_cap_status，能力状态的回调函数

 * @version 1.0.0
 * @author Alex Cui
 * @date   2023-09-14
 *
 * CHANGELOG:
 * DATE             AUTHOR          REASON
 * 2023-09-14       Alex Cui    Init version;
 */
//socket通信头文件，位于luatos-soc-2022\PLAT\middleware\thirdparty\lwip\src\include\lwip
#include "common_api.h"
#include "sockets.h"
#include "dns.h"
#include "lwip/ip4_addr.h"
#include "netdb.h"


//LuatOS的头文件, 位于LuatOS库的luat/include
#include "luat_base.h"   // 基础函数
#include "luat_malloc.h" // 内存分配函数
#include "luat_log.h"    // 日志函数,配套LUAT_LOG_TAG
#include "luat_debug.h"
#include "luat_rtos.h"

#define LUAT_LOG_TAG "custom"

#include "qxwz_sdk.h"
#include "qxwz_types.h"
#include "qxwz_user_impl.h"

static qxwz_uint32_t sdk_auth_flag = 0;
static qxwz_uint32_t sdk_start_flag = 0;
static qxwz_sdk_cap_info_t sdk_cap_info = {0};


/*
qxwz_sock_host_t sock_host = {
    char hostname = "
    int port = 0
};
*/

/*
 *Log
 */
qxwz_int32_t qxwz_printf(const qxwz_char_t *fmt, ...)
{
    // 这里的可变参数如何处理？
    LLOGD(fmt);
    return 0;
};

/*
 * Mutex lock
 */

qxwz_int32_t qxwz_mutex_init(qxwz_mutex_t *mutex)
{
    return luat_rtos_mutex_create(&mutex);
};

qxwz_int32_t qxwz_mutex_trylock(qxwz_mutex_t *mutex)
{
    return luat_rtos_mutex_lock(mutex, 100);
};

qxwz_int32_t qxwz_mutex_lock(qxwz_mutex_t *mutex)
{
    return luat_rtos_mutex_lock(mutex, 100);
};

qxwz_int32_t qxwz_mutex_unlock(qxwz_mutex_t *mutex)
{
    return luat_rtos_mutex_unlock(mutex);
};

qxwz_int32_t qxwz_mutex_destroy(qxwz_mutex_t *mutex)
{
    return luat_rtos_mutex_delete(mutex);
};

/*
 * Memory
 */
qxwz_void_t *qxwz_malloc(qxwz_ulong32_t size)
{
    LUAT_DEBUG_PRINT("qxwz sdk memory malloc");
    return luat_heap_malloc(size);
};

qxwz_void_t *qxwz_calloc(qxwz_uint32_t nmemb, qxwz_uint32_t size)
{
    LUAT_DEBUG_PRINT("qxwz sdk memory calloc");
    return luat_heap_calloc(nmemb , size);
};

qxwz_void_t *qxwz_realloc(qxwz_void_t *ptr, qxwz_uint32_t size)
{
    LUAT_DEBUG_PRINT("qxwz sdk memory realloc");
    return luat_heap_realloc(ptr, size);
};

qxwz_void_t qxwz_free(qxwz_void_t *ptr)
{
    LUAT_DEBUG_PRINT("qxwz sdk memory free");
    luat_heap_free(ptr);
};

// =====================================SOCKET API==================================
/*
 * Create a socket handler.
 *
 * @return:
 *    >= 0: succeeds, identifier of the socket handler;
 *      -1: failed;
 */
qxwz_int32_t qxwz_sock_create(void)
{
    LUAT_DEBUG_PRINT("qxwz sdk socket create");

    // TCP协议, 不用关心参数中定义的常量
    // #define socket(domain,type,protocol)              lwip_socket(domain,type,protocol)
    //return socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    int socket_id = -1;
    socket_id = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);		//创建套接字 IPV4 流式套接字 TCP
	fcntl(socket_id, F_SETFL, O_NONBLOCK);			//设置套接字为非阻塞模式，对此套接字的操作（如 connect 和 read）不会导致调用者阻塞

    //socket_id=-1,创建失败；socket_id>=0,创建成功
    return socket_id;

};


/*
 * Establish a connection to the server specified by the argument `serv` in a blocking way.
 * @param[in]  sock: socket handler;
 * @param[in]  serv: the server info, see `qxwz_sock_host_t`;
 *
 * @return:
 *      0: connecting succeeded;
 *     -1: connecting failed;
 */
qxwz_int32_t qxwz_sock_connect(qxwz_int32_t sock, qxwz_sock_host_t *serv)
{
    LUAT_DEBUG_PRINT("qxwz sdk socket connect %s:%d",serv->hostname,serv->port);
    // 这里需要对两个函数的参数做转换
    // #define connect(s,name,namelen)                   lwip_connect(s,name,namelen)
    //return connect(sock, (const struct sockaddr *)&name, sockaddr_t_size);
    //DNS解析
    ip_addr_t remote_ip={0};								//结构体，存放主机网络地址
    struct sockaddr_in name;							//网络地址
    socklen_t sockaddr_t_size = sizeof(name);			//socket长度
    fd_set write_set,error_set;				//在使用 select 函数进行多路I/O复用时，fd_set 结构体被用来指定 select 函数需要检查的文件描述符集合
    int ret;
    struct timeval to;									//在网络编程中，timeval 结构体常用于指定时间间隔或时间戳，如在 select 函数中用于指定超时时间
    struct hostent dns_result;							//表示主机信息的一个标准数据结构，hostent 结构体通常用于解析主机名
    struct hostent *p_result;
	int h_errnop;

    char buf[128] = {0};
    ret = lwip_gethostbyname_r(serv->hostname, &dns_result, buf, 128, &p_result, &h_errnop);		//返回一个int，从中读取IP地址和其他相关信息
    if (!ret)
    {
        remote_ip = *((ip_addr_t *)dns_result.h_addr_list[0]);		//主机网络地址列表
    }
    else
    {
        //DNS解析失败，等待后重试
        luat_rtos_task_sleep(1000);
        LUAT_DEBUG_PRINT("dns fail");
    }

    //初始化一个 sockaddr_in 结构体（称为 name）以用于套接字绑定
    memset(&name, 0, sizeof(name));
    name.sin_family = AF_INET;
    name.sin_addr.s_addr = IPADDR_ANY;
    name.sin_port = 0;

    //绑定套接字
    bind(sock, (const struct sockaddr *)&name, sockaddr_t_size);

    //设置服务器地址和端口
    name.sin_family = AF_INET;
    name.sin_addr.s_addr = remote_ip.u_addr.ip4.addr;		//设置为服务器的 IP 地址
    name.sin_port = htons(serv->port);		//设置为服务器的端口号，使用 htons 函数将主机字节序转换为网络字节序
    //使用 connect 函数发起对服务器的连接，由于套接字被设置为非阻塞模式，connect 可能会在连接实际建立之前返回
    ret = connect(sock, (const struct sockaddr *)&name, sockaddr_t_size);

    //设置超时时间为30s
    to.tv_sec = 30;
    to.tv_usec = 0;

    //初始化文件描述符集
    FD_ZERO(&write_set);
    FD_ZERO(&error_set);
    FD_SET(sock,&write_set);		//检查套接字是否可写
    FD_SET(sock,&error_set);		//检查是否有错误发生

    return select(sock+1, NULL, &write_set, &error_set, &to);

    /*
    //检查套接字状态
    ret = select(sock+1, NULL, &write_set, &error_set, &to);

    //如果 select 返回值(ret) 小于 0，则表示出错；返回 0 表示超时；大于 0 表示套接字已准备好进行某些操作
    if(ret<=0)
    {
        return -1;
    }
    else
    {
        if(FD_ISSET(sock, &error_set))
		{
			return -1;
		}
		else if(FD_ISSET(sock, &write_set))
		{
			return 0;
		}
    }
    */
};



/*
 * Send data in a non-blocking way.
 * @param[in]  sock: socket handler;
 * @param[in]  send_buf: pointer of the buffer to be sent;
 * @param[in]  len: length of the buffer;
 *
 * @return:
 *    >=0: the number of bytes sent;
 *     -1: fails;
 */
qxwz_int32_t qxwz_sock_send(qxwz_int32_t sock, const qxwz_uint8_t *send_buf, qxwz_uint32_t len)
{
    LUAT_DEBUG_PRINT("qxwz sdk socket send %d bytes",len);
    return send(sock, send_buf, len, 0);
};


/*
 * Receive data in a non-blocking way.
 * @param[in]  sock: socket handler;
 * @param[in]  recv_buf: pointer of the buffer to recv the data;
 * @param[in]  len: length of the buffer;
 *
 * @return:
 *     >0: the number of bytes received;
 *      0: connection is closed by peer
 *     -1: no data received this time
 *     -2: error occur;
 */
qxwz_int32_t qxwz_sock_recv(qxwz_int32_t sock, qxwz_uint8_t *recv_buf, qxwz_uint32_t len)
{
    LUAT_DEBUG_PRINT("qxwz sdk socket received %d bytes",len);
    return recv(sock, recv_buf, len, 0);
};


/*
 * Close a socket handler.
 * @param[in]  sock: socket handler.
 *
 * @return:
 *      0: succeeds;
 *     -1: fails;
 */
qxwz_int32_t qxwz_sock_close(qxwz_int32_t sock)
{
    LUAT_DEBUG_PRINT("qxwz sdk socket close");
	return close(sock);
};




static qxwz_void_t demo_show_caps(qxwz_sdk_cap_info_t *cap_info)
{
    qxwz_uint32_t loop = 0;

    LUAT_DEBUG_PRINT("total capabilities: %d\n", cap_info->caps_num);
    for (loop = 0; loop < cap_info->caps_num; ++loop) {
        LUAT_DEBUG_PRINT("idx: %d, cap_id: %u, state: %d, act_method: %d, expire_time: %llu\n",
            loop + 1,
            cap_info->caps[loop].cap_id,
            cap_info->caps[loop].state,
            cap_info->caps[loop].act_method,
            cap_info->caps[loop].expire_time);
    }
}

static qxwz_void_t demo_on_auth(qxwz_int32_t status_code, qxwz_sdk_cap_info_t *cap_info) {
    if (status_code == QXWZ_SDK_STAT_AUTH_SUCC) {
        sdk_auth_flag = 1;
        sdk_cap_info = *cap_info;
        demo_show_caps(cap_info);
    } else {
        LUAT_DEBUG_PRINT("auth failed, code=%d\n", status_code);
    }
}

static qxwz_void_t demo_on_start(qxwz_int32_t status_code, qxwz_uint32_t cap_id) {
    LUAT_DEBUG_PRINT("on start cap:status_code=%d, cap_id=%d\n", status_code, cap_id);
    sdk_start_flag = 1;
}


static qxwz_void_t demo_on_status(int code)
{
    LUAT_DEBUG_PRINT(" on status code: %d\n", code);
}

static qxwz_void_t demo_on_data(qxwz_uint32_t type, const qxwz_void_t *data, qxwz_uint32_t len)
{
    LUAT_DEBUG_PRINT(" on data: %d, ptr: %p, len: %d\n", type, data, len);

    switch (type) {
        case QXWZ_SDK_DATA_TYPE_RAW_NOSR:
            LUAT_DEBUG_PRINT("QXWZ_SDK_DATA_TYPE_RAW_NOSR\n");
            break;
        default:
            LUAT_DEBUG_PRINT("unknown type: %d\n", type);
    }
}
